#include "client_loop.h"
//  ​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​
//  * 主事件循环 - epoll驱动的状态机
//  *   IDLE --发送命令--> AWAIT_RESP --收到响应--> IDLE
//  ​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​*​**​
int run_client_loop(ClientContext *ctx,const char *ip, int port)
{
    NetAddr_t net_addr = { ip, port };  // 设置长命令传输线程所需的服务端套接字
    int epfd = epoll_create(1);
    epollAdd(epfd,ctx->sockfd);         //  监听套接字--服务端响应 
    epollAdd(epfd,STDIN_FILENO);        //  监听套接字--服务端响应 
    struct epoll_event readyset[1024];
    ParsedCommand command;
    protocol_header_t header;
    char response_buf[MAX_BUF_SIZE];    // 接收响应
    char input[MAX_BUF_SIZE];           // 接收用户输入
    
    // 清空所有的recv缓冲
    // int ret = 1;
    // while(ret != 0){
    //     ret = receive_response(ctx, &header, response_buf,sizeof(response_buf));
    // }

    ctx->state = STATE_IDLE;
    printf("~客户端已就绪，开始使用吧~  退出请输入：quit或exit~\n");

    while (1)
    {
        if(ctx->state == STATE_IDLE)
        {   // 命令输入交互提示
            printf("\n\n请输入命令-->");
            fflush(stdout);
        }
        
        int readynum = epoll_wait(epfd, readyset, 1024, -1);// 等待事件（无超时）

        // 处理所有就绪事件
        for (int i = 0; i < readynum; i++)
        {
            //printf("handle readyset.\n");

            /* 用户输入事件处理 */
            if (readyset[i].data.fd == STDIN_FILENO && ctx->state == STATE_IDLE)
            {   // stdin就绪且是等待用户输入情况
                // printf("get stdin\n");
                bzero(input,MAX_BUF_SIZE);

                if (fgets(input, sizeof(input), stdin) == NULL) continue;
                input[strcspn(input, "\n")] = '\0';// 去除换行符

                // printf("get user input: %s\n",input);

                // 解析用户输入：退出返回值为1，未知命令返回值为-1
                parse_input(input, &command);

                // 命令解析测试：
                // printf("command:%d , args:",command.cmd_type);
                // if(strlen(command.args) == 0){ printf("(null)\n");}
                // else{printf("%s\n",command.args);}
                // while(1){ sleep(1); }

                // 退出命令处理
                if (command.cmd_type == CMD_EXIT)
                {
                    printf("Goodbye!\n");
                    return 0;
                }
                // 未知命令处理
                else if (command.cmd_type == CMD_ILLEGAL)
                {
                    printf("Unknown command. Type 'help' for usage.\n");
                    continue;
                }

                // 长命令处理--文件上传下载
                // 长命令不涉及状态转移，以实现长命令处理时可接收新的短命令。
                if(command.cmd_type == CMD_GETS && command.has_args){
                    printf("开始文件下载 -- \n");
                    fileTransThread(downloadThreadFunc,ctx, net_addr, command);// 文件下载命令处理
                }
                else if(command.cmd_type == CMD_PUTS && command.has_args){
                    printf("开始文件上传 -- \n");
                    fileTransThread(uploadThreadFunc,ctx, net_addr, command);// 文件上传命令处理
                }
                else{
                    // 短命令处理--发送命令到服务器
                    if (send_command(ctx, command.cmd_type, command.has_args ? command.args : NULL,
                                    command.has_args ? strlen(command.args) : 0) != 0)
                    {   // send_command函数返回值为0 时表示发送成功，否则失败
                        printf("Send command failed\n");
                        ctx->state = STATE_ERROR;       // 状态转移
                    }
                    else
                    {
                        ctx->state = STATE_AWAIT_RESP;  // 状态转移
                    }
                }
            }
            /* 服务器响应短命令的事件处理 */
            else if (readyset[i].data.fd == ctx->sockfd && ctx->state == STATE_AWAIT_RESP)
            {   // 接收服务端响应
                bzero(response_buf,MAX_BUF_SIZE);
                bzero(&header,sizeof(header));
                int ret = receive_response(ctx, &header, response_buf,
                          sizeof(response_buf));
                if (ret != 0)
                {
                    printf("Receive error: %d\n", ret);
                    ctx->state = STATE_ERROR;
                    continue;
                }

                // 验证响应消息是否接收正确
                printf("~已接收来自服务端的响应~\n");
                //printf("cmd = %d, resptype = %d, datalen = %d\n",header.command,header.resp_type,header.data_length);

                if( header.resp_type == SUCESS ){
                    printf("响应内容：操作成功\n");
                    // 服务端处理成功
                    switch (header.command){
                    case CMD_LS:// 显示目录信息
                        printf("\n");
                        handle_ls_response(ctx, &header, response_buf);
                        break;
                    case CMD_CD:// 切换目录
                        printf("目录切换成功.\n");
                        // handle_cd_response(ctx, &header,response_buf);
                        break;
                    case CMD_PWD:// 获取当前工作目录
                        printf("当前工作目录: %s\n",response_buf);
                        break;
                    case CMD_RMOVE: // 移除文件
                        printf("移除文件成功.\n");
                        break;
                    case CMD_MKDIR: // 创建目录
                        printf("创建目录成功.\n");
                        break;
                    case CMD_RMDIR: // 移除目录
                        printf("移除目录成功.\n");
                        break;
                    default:
                        printf("来自服务端的未知回复: %d\n", header.command);
                    }
                }
                else{
                    printf("响应内容：操作失败.\n");
                    ctx->state = STATE_ERROR;
                }
                ctx->state = STATE_IDLE; // 状态转移
            }
        }
    }
    return 0;
}

/* 创建子线程
    子线程完成以下任务：
        与服务端建立TCP连接
        发送token验证
        if（验证通过）{
            下载文件到本地；    // 直接调用函数
        }
        else{
            // 验证失败，断开连接并返回-1
        }
*/
int fileTransThread(ThreadFunc thread_func,ClientContext *ctx, NetAddr_t net_addr, ParsedCommand command){
    
    // 创建子线程，子线程调用download_thread函数，传入参数-->重新设计，解耦
    Tunnel_t *tunnel_message = (Tunnel_t *)calloc(1,sizeof(Tunnel_t));
    if(tunnel_message == NULL) {
        perror("文件下载: 分配隧道内存失败");
        return -1;
    }
    ClientContext *tnnnel_ctx = (ClientContext *)calloc(1,sizeof(ClientContext));    // 用来处理新的连接，主要是线程使用新的sockfd，函数调用结束后会被释放
    if(tnnnel_ctx == NULL) {
        perror("文件下载: 分配隧道内存失败");
        return -1;
    }
    *tnnnel_ctx = *ctx;// 只拷贝值。

    // tunnel信息初始化
    tunnel_message->ctx = tnnnel_ctx;
    tunnel_message->command = command;
    tunnel_message->net_addr = net_addr;

    pthread_t tid;
    if( pthread_create(&tid,NULL,thread_func,tunnel_message) !=0 ){
        // 线程创建失败
        perror("文件下载: 线程创建失败.\n");
        free(tnnnel_ctx);
        free(tunnel_message);
        return -1;
    }

    // 分离线程，线程执行结束后自动释放资源
    pthread_detach(tid);

    return 0;
}

// 线程入口函数
void* downloadThreadFunc(void* arg){
    Tunnel_t *tunnel = (Tunnel_t *)arg;

    // 创建临时TCP连接
    if( init_client_noblock(tunnel->ctx, tunnel->net_addr.ip,tunnel->net_addr.port) !=0 ){
        printf("文件下载：服务端网络连接失败.\n");
        close(tunnel->ctx->sockfd);
        free(tunnel->ctx);
        free(tunnel);
        pthread_exit(NULL);
    }
    printf("文件下载: 临时连接已建立.\n");

    // token验证
    char *filename = tunnel->command.args;
    if(send_command(tunnel->ctx,tunnel->command.cmd_type,filename,strlen(filename)) != 0){
        printf("文件下载:命令发送失败.\n");
        free(tunnel->ctx);
        free(tunnel);
        return NULL;
    }

    // 文件下载
    printf("命令发送成功，开始下载文件.\n");
    if( download(tunnel->ctx->sockfd,filename) !=0 ){
        printf("文件下载失败.\n");
    }
    else{
        printf("文件下载成功.\n");
    }
    close(tunnel->ctx->sockfd);
    free(tunnel->ctx);
    free(tunnel);
    return NULL;
}
void* uploadThreadFunc(void* arg){
    Tunnel_t *tunnel = (Tunnel_t *)arg;

    // 创建临时TCP连接
    if( init_client_noblock(tunnel->ctx, tunnel->net_addr.ip,tunnel->net_addr.port) !=0 ){
        printf("文件上传：服务端网络连接失败.\n");
        close(tunnel->ctx->sockfd);
        free(tunnel->ctx);
        free(tunnel);
        pthread_exit(NULL);
    }
    printf("文件上传: 临时连接已建立.\n");

    // token验证
    char *filename = tunnel->command.args;
    if(send_command(tunnel->ctx,tunnel->command.cmd_type,filename,strlen(filename)) != 0){
        printf("文件上传: 命令发送失败.\n");
        free(tunnel->ctx);
        free(tunnel);
        return NULL;
    }

    // 文件下载
    printf("命令发送成功，开始上传文件.\n");
    printf("file_name = %s\n",filename);
    if( upload(tunnel->ctx->sockfd,filename) !=0 ){
        printf("文件下载失败.\n");
    }
    else{
        printf("文件上传成功.\n");
    }
    close(tunnel->ctx->sockfd);
    free(tunnel->ctx);
    free(tunnel);
    return NULL;
}